We know that we can use an interrupt signal to stop the foreground program and perform another piece of code. In our case we want use an interrupt to refresh the LEDs at regular intervals. However, at the moment we don't know how to cause the interrupt. This problem is solved by another hardware component in the PICmicro, the timer. This is simply an eight bit counter. It counts up at a particular rate and when it wraps round (i.e. goes from 0xff to 0x00) it can be made to cause an interrupt. The counter "ticks" at the PICmicro clock frequency divided by four. This is because the PICmicro takes four clock ticks to obey a single instruction, and the clock ticks at the rate instructions are performed.
You can control the rate at which the timer counts, i.e. how long it takes to wrap round, by means of a pre-scaler. This is a hardware device which divides the clock coming into the timer by a particular power of two. If the prescaler is set to 2 the timer is clocked at half speed, if the prescaler is set to 4 the timer is clocked at quarter speed, and so on. The PICmicro gives us a pre-scaler so that we can control how long it takes the counter to overflow.
We will look at the programming of the prescaler a little later, for now we can pick a value which gives us a resonable refresh rate without taking too much time away from the foreground program. The value I have chosen gives an interrupt of 200 Hz. This means that we will get round all of the LEDs in 200/4 = 50Hz. This is the same frequency as domestic lighting and so to the human eye is completely flicker free.
The code on the right turns on the interrupts and puts a call of the refresh function in the timer interrupt handler. Note that in the main function the code actually does nothing and yet the lights still work!
If you load and run Exercise 4.4 you will find that the numbers are displayed without the foreground program doing anything!
You can use the timer interrupt for anything which needs to happen at regular intervals. It can serve as a replacement for the delay loops which we use for our music program, and also allow notes to continue playing a sound while the program does something else.
Foreground and Background
I call the program which is actually running (i.e. the contents of the main function) the foreground process and the code which runs on the interrupts the background process.
You have to be careful how the foreground and background processes interact. At the moment our foreground function does nothing, in that it is just a while loop with no contents. If it was to change variables used by the timer interrupt, in this case the variable led_counter, it could interfere with the screen refresh.
When you use interrupts in this way it is important that you set up well defined connections between the foreground and background processes. In this case we use the array which holds the patterns for each LED as the communications medium. We also know that the foreground only ever puts values into the array, and the background only ever reads them. If we want to have communication in both directions between the foreground and background processes you must be careful how you synchronise it.
Operating systems like Windows 98, NT and Linux use the timer interrupt to switch execution between more than one "simultaneously" executing program. You can also do this kind of thing in the PICmicro if you wish.
Accessing INTCON
In the PIC, interrupts are controlled using the INTCON register, which lives in the same area as the other registers we've used so far, such as PORTA and TRISB.
For full details, look in the C programming section at 'C and hardware'
/* EX 4.4 LED interrupt refresh */
/* Rob Miles January 2000 */
/* Uses 4 digit LED display */
/* connected to PORTA and PORTB */
/* Include the file containing */
/* the definition of INTCON */
#include <system.h>
void setup_hardware (void)
{
/* set all of PORTB for output */
TRISB = 0x00 ;
/* set all of PORTA for output */
TRISA = 0xe0 ;
/* Configure the INTCON register */
/* to enable timer interrupts */
INTCON = 0b10100000;
/* Configure the timer to use the prescaler */
OPTION_REG = 0b01000010 ;
}
/* a value for each bit in PORTA. */
/* We can feed in the number of */
/* the LED and it will give us */
/* the value to put into A */
const unsigned char enable [4] =
{
1, 2, 4, 8
} ;
/* these are the patterns for the */
/* LEDs which were worked out */
/* from the datasheet. Note that */
/* to light a LED the bit on */
/* PORTB must be low */
/* I can use this array to */
/* convert from a digit to the 7 */
/* segments needed */
const unsigned char patterns [10] =
/* 0 1 2 3 4 5 */
{0xc0,0xf9,0xa4,0xb0,0x99,0x92,
/* 6 7 8 9 */
0x83,0xf8,0x80,0x98 } ;
/* number of LEDs in our display */
#define DISPLAY_SIZE 4
/* segment patterns for our LEDs */
unsigned char
segments [DISPLAY_SIZE] ;
/* counter used by our refresh */
unsigned char led_counter = 0 ;
/* each time refresh is called it */
/* sets the display for a led and */
/* moves on to the next */
void refresh ( void )
{
/* turn off all the LEDs */
PORTA = 0 ;
/* set segments for the led */
PORTB segments [led_counter] ;
/* turn the led on */
PORTA = enable [led_counter ] ;
/* move on to the next led */
led_counter = led_counter + 1 ;
/* see if we fell off the end */
if ( led_counter == DISPLAY_SIZE )
{
led_counter = 0 ;
}
}
/* display just loads the pattern */
/* into the segment. refresh will */
/* read it later */
void display ( unsigned char digit,
unsigned char pos )
{
segments [pos] = patterns [digit];
}
/* timer Overflow handler */
/* called by the interrupt code */
/* when a timer overflow occurs */
void tmrHandler( void )
{
refresh () ;
}
/* This is the interrupt handler */
/* It is called when the PICmicro */
/* detects an interrupt */
/* It checks the status bits to */
/* find out who caused the */
/* interrupt and then calls that */
/* handler */
unsigned char counter = 0;
void interrupt( void )
{
/* if the timer has overflowed */
/* bit 2 of INTCON is set high */
if( INTCON & 4 )
{
/* clear the bit to turn */
/* off this interrupt */
clear_bit( INTCON, 2 );
counter = counter + 1;
if ( counter > 11 )
{
counter = 0;
/* call the handler */
/* function */
tmrHandler();
}
}
}
void main ( void )
{
setup_hardware () ;
/* display the values */
display ( 0, 0 ) ;
display ( 1, 1 ) ;
display ( 2, 2 ) ;
display ( 3, 3 ) ;
while (1)
{
/* do nothing here! */
}
}